/* Yet Another Experimental Software Renderer
 *
 * Copyright (c) 2010 Pawel "Hery" Dziepak
 */

#include "rasterizer.h"
#include "output_merger.h"
#include "pixel_shader.h"
#include "utils.h"

#include <math.h>

using namespace yaesr;

void default_rasterizer::rasterize_points(std::vector<point> &vbo) {
	#pragma omp parallel for shared(vbo)
	for (int i = 0; i < vbo.size(); i++) {
		draw_point(vbo[i].vert);
	}
}

void default_rasterizer::rasterize_lines(std::vector<line> &vbo) {
	#pragma omp parallel for shared(vbo)
	for (int i = 0; i < vbo.size(); i++) {
		draw_line(vbo[i].vert0, vbo[i].vert1);
	}
}

vertex default_rasterizer::interpolate_line(vertex a, vertex b, double mix) {
	vertex vert;

	vert.position = lerp(a.position, b.position, mix);
	vert.color0 = lerp(a.color0, b.color0, mix);
	vert.texcoord0 = lerp(a.texcoord0, b.texcoord0, mix);

	return vert;
}

vertex default_rasterizer::interpolate_triangle(vertex a, vertex b, vertex c, double alpha, double beta, double gamma) {
	vertex vert;

	vert.position = lerp3(a.position, alpha, b.position, beta, c.position, gamma);
	vert.color0 = lerp3(a.color0, alpha, b.color0, beta, c.color0, gamma);
	vert.texcoord0 = lerp3(a.texcoord0, alpha, b.texcoord0, beta, c.texcoord0, gamma);

	return vert;
}

void default_rasterizer::draw_point(vertex vert) {
	int x = int((vert.position.x + 1.0) / 2.0 * next->get_width());
	int y = int((vert.position.y + 1.0) / 2.0 * next->get_height());
	draw_point(x, y, vert);
}

void default_rasterizer::draw_point(int x, int y, vertex vert) {
	vert.position.x = x / (double)next->get_width() * 2.0 - 1.0;
	vert.position.y = y / (double)next->get_height() * 2.0 - 1.0;
	next->draw(x, y, pixel_sdr->execute(vert), vert.position.z);
}

void default_rasterizer::draw_line(vertex vert0, vertex vert1) {
	int start_x = int((vert0.position.x + 1.0) / 2.0 * next->get_width());
	int start_y = int((vert0.position.y + 1.0) / 2.0 * next->get_height());
	int stop_x = int((vert1.position.x + 1.0) / 2.0 * next->get_width());
	int stop_y = int((vert1.position.y + 1.0) / 2.0 * next->get_height());

	double total_distance = sqrt(pow(start_x - stop_x, 2.0) + pow(start_y - stop_y, 2.0));

	// Bresenham Algorithm
	bool steep = abs(stop_y - start_y) > abs(stop_x - start_x);
	if (steep) {
		swap(start_x, start_y);
		swap(stop_x, stop_y);
	}
	if (start_x > stop_x) {
		swap(start_x, stop_x);
		swap(start_y, stop_y);

		vertex temp = vert0;
		vert0 = vert1;
		vert1 = temp;
	}
	int deltax = stop_x - start_x;
	int deltay = abs(stop_y - start_y);
	int error = deltax / 2;
	int ystep;
	int y = start_y;
	if (start_y < stop_y)
		ystep = 1;
	else
		ystep = -1;

	for (int x = start_x; x <= stop_x; x++) {

		double distance = sqrt(pow(start_x - x, 2.0) + pow(start_y - y, 2.0));

		if (steep)
			draw_point(y, x, interpolate_line(vert0, vert1, distance / total_distance));
		else
			draw_point(x, y, interpolate_line(vert0, vert1, distance / total_distance));

		error -= deltay;
		if (error < 0) {
			y = y + ystep;
			error += deltax;
		}
	}
}

void default_rasterizer::rasterize_triangles(std::vector<triangle> &vbo) {
	#pragma omp parallel for shared(vbo)
	for (int i = 0; i < vbo.size(); i++) {
		draw_triangle(vbo[i].vert0, vbo[i].vert1, vbo[i].vert2);
	}
}

double fx(vertex a, vertex b, double x, double y) {
	return (a.position.y - b.position.y) * x + (b.position.x - a.position.x) * y + a.position.x * b.position.y - b.position.x * a.position.y;
}

void default_rasterizer::draw_triangle(vertex vert0, vertex vert1, vertex vert2) {
	int x0 = int((vert0.position.x + 1.0) / 2.0 * next->get_width());
	int y0 = int((vert0.position.y + 1.0) / 2.0 * next->get_height());

	int x1 = int((vert1.position.x + 1.0) / 2.0 * next->get_width());
	int y1 = int((vert1.position.y + 1.0) / 2.0 * next->get_height());

	int x2 = int((vert2.position.x + 1.0) / 2.0 * next->get_width());
	int y2 = int((vert2.position.y + 1.0) / 2.0 * next->get_height());

	// find bounding box
	int x_max = max(x0, max(x1, x2));
	int x_min = min(x0, min(x1, x2));
	int y_max = max(y0, max(y1, y2));
	int y_min = min(y0, min(y1, y2));

	double alpha_div = fx(vert1, vert2, vert0.position.x, vert0.position.y);
	double beta_div = fx(vert2, vert0, vert1.position.x, vert1.position.y);
	double gamma_div = fx(vert0, vert1, vert2.position.x, vert2.position.y);

	// Barycentric coordinates based rasterization
	for (int x = x_min; x <= x_max; x++) {
		double x_ndc = (double)x / (double)next->get_width() * 2.0 - 1.0;

		for (int y = y_min; y <= y_max; y++) {
			double y_ndc = (double)y / (double)next->get_height() * 2.0 - 1.0;

			double alpha = fx(vert1, vert2, x_ndc, y_ndc) / alpha_div;
			double beta = fx(vert2, vert0, x_ndc, y_ndc) / beta_div;
			double gamma = fx(vert0, vert1, x_ndc, y_ndc) / gamma_div;

			if (alpha <= 1.0 && beta <= 1.0 && gamma <= 1.0 && alpha >= 0.0 && beta >= 0.0 && gamma >= 0.0)
				draw_point(x, y, interpolate_triangle(vert0, vert1, vert2, alpha, beta, gamma));
		}
	}
}